package com.example.demo;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import static org.springframework.ui.freemarker.FreeMarkerTemplateUtils.processTemplateIntoString;

@RestController
public class FileDownloadController {
    static final Logger log = LoggerFactory.getLogger(FileDownloadController.class);
    private final DownloadHandler downloadHandler;

    public FileDownloadController(DownloadHandler downloadHandler) {
        this.downloadHandler = downloadHandler;
    }

    @GetMapping(path = "/")
    public ResponseEntity<ResponseBodyEmitter> download() throws Exception {
        ResponseBodyEmitter bodyEmitter = new ResponseBodyEmitter(
                TimeUnit.MINUTES.toMillis(2) /* timeout */);
        log.info("Start emitting...");
        downloadHandler.download2(bodyEmitter);
        log.info("End emitting...");
        return ResponseEntity //
                .status(HttpStatus.OK) //
                .contentType(MediaType.APPLICATION_OCTET_STREAM) //
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=abc.xls") //
                .body(bodyEmitter);
    }

    @Component
    @Transactional(readOnly = true)
    static class DownloadHandler {
        static final Logger log = LoggerFactory.getLogger(DownloadHandler.class);
        final JdbcTemplate jdbcTemplate;

        public DownloadHandler(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }

        @Autowired
        Configuration configuration; //freeMarker configuration

        @Async
        public void download2(ResponseBodyEmitter emitter) throws InterruptedException {
            Map<String, Object> model = new HashMap<String, Object>();
            model.put("time", new Date());
            model.put("message", "这是测试的内容。。。");
            model.put("toUserName", "张三");
            model.put("fromUserName", "老许");
            model.put("orderNo", "432432153253");
            model.put("merBillNo", "432432153253");
            model.put("updateTime", new Date());
            model.put("accountAmt", "432432153253");
            model.put("codeAmt", "432432153253");
            model.put("realPay", "432432153253");
            model.put("invoicePrice", "432432153253");
            model.put("mobile", "13800138000");
            model.put("codeNos", "432432153253");
            model.put("statusName", "已退款");

            model.put("csv_code", "onegolist");

            Template t = null; // freeMarker template
            Thread crawlThread = null;
            try {
                t = configuration.getTemplate("head.ftl");
                String content = processTemplateIntoString(t, model);
                emitter.send(content);
                log.error(content);
                int rowIndex = 0;
                int sheetIndex = 1;
                final BlockingQueue<DayData> queue = new ArrayBlockingQueue(3);
                int[] executeCondation = new int[2];//第一位表示是否执行完毕，第二位表示执行状态
                crawlThread = new Thread("craw-" + System.currentTimeMillis()) {
                    public void run() {
                        try {
                            for (int i = 0; i < 10; i++) {
                                List<Map<String, Object>> resultList = new LinkedList<>();
                                for (int j = 0; j < 10000; j++) {
                                    resultList.add(model);
                                }
                                queue.offer(new DayData(resultList), 2, TimeUnit.MINUTES);//不能下载超时2分钟
                                TimeUnit.SECONDS.sleep(2);//模拟取一天的数据需要3秒钟
                            }
                        } catch (Exception e) {
                            log.error("crawl error:{}", e);
                            executeCondation[1] = 1;
                            return;//退出run方法，中断线程
                        } finally {
                            log.debug("crawl complete");
                            executeCondation[0] = 1;//mark execute complete
                        }
                    }
                };
                crawlThread.start();
                while (1 != executeCondation[0]) {
                    DayData dataDay = queue.poll(2, TimeUnit.SECONDS);
                    if (null == dataDay) {
                        continue;
                    }
                    long start = System.currentTimeMillis();
                    for (Map<String, Object> data : dataDay.getItemList()) {
                        rowIndex++;
                        t = configuration.getTemplate("main_iterator.ftl"); // freeMarker template
                        content = processTemplateIntoString(t, data);
                        emitter.send(content);
                        if (rowIndex > 65500) {
                            rowIndex = 1;
                            sheetIndex++;
                            model.put("sheetName", sheetIndex);
                            emitter.send(processTemplateIntoString(configuration.getTemplate("new_sheet.ftl"), data));
                        }
                    }
                    log.error("--------------------------:{}", (System.currentTimeMillis() - start) / 1000L);
                }
                if (1 == executeCondation[1]) {
                    emitter.send(processTemplateIntoString(configuration.getTemplate("error.ftl"), null));
                }
                t = configuration.getTemplate("footer.ftl"); // freeMarker template
                content = processTemplateIntoString(t, model);
                emitter.send(content);
                log.error(content);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TemplateException e) {
                e.printStackTrace();
            } finally {
                if (null != crawlThread) {
                    crawlThread.interrupt();
                }
            }
            log.info("End downloading...");
            emitter.complete();
        }

        @Async
        public void download1(ResponseBodyEmitter emitter) {
            log.info("Start downloading...");
            for (int i = 0; i < 31; i++) {
                for (int j = 0; j < 1; j++) {
                    try {
                        emitter.send("idx," + i + ",no," + j + "\n");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("End downloading...");
            emitter.complete();

        }

        public void download(ResponseBodyEmitter emitter) {
            log.info("Start downloading...");

            this.jdbcTemplate.query("SELECT id FROM demo", rs -> {
                Integer id = rs.getInt("id");
                log.trace("id,{}", id);
                try {
                    emitter.send("id," + id + "\n");
                    if (0 == id % 5) {
                        TimeUnit.MILLISECONDS.sleep(10);
                    }
                } catch (IOException e) {
                    log.info("IO Exception!");
                    emitter.completeWithError(e);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            log.info("End downloading...");
            emitter.complete();
        }
    }
}

/**
 * 每天的数据
 */
class DayData {
    private List<Map<String, Object>> itemList;

    public DayData(List<Map<String, Object>> itemList) {
        this.itemList = itemList;
    }

    public List<Map<String, Object>> getItemList() {
        return itemList;
    }

    public void setItemList(List<Map<String, Object>> itemList) {
        this.itemList = itemList;
    }
}